﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;

namespace Jayden.Dll.Web.Json
{
	public sealed class JsonHelper
	{
		private JsonHelper() { }

		public const string MimeType = "application/json";

		public const string True = "true";
		public const string False = "false";
		public const string Null = "null";

		public static string ParseString(string jsonText)
		{
			if (Null.Equals(jsonText))
				return null;
			// remove quotes
			string text = jsonText.Substring(1, jsonText.Length - 2);
			return text
				.Replace("\\\\", "\\")
				.Replace("\\\"", "\"")
				.Replace("\\/", "/")
				.Replace("\\b", "\b")
				.Replace("\\f", "\f")
				.Replace("\\n", "\n")
				.Replace("\\r", "\r")
				.Replace("\\t", "\t");
			// todo: add unicode support
		}
		public static string EncodeString(string text)
		{
			if (text == null)
				return Null;
			return "\"" + text
				.Replace("\\", "\\\\")
				.Replace("\"", "\\\"")
				.Replace("/", "\\/")
				.Replace("\b", "\\b")
				.Replace("\f", "\\f")
				.Replace("\n", "\\n")
				.Replace("\r", "\\r")
				.Replace("\t", "\\t") + "\"";
			// todo: add unicode support
		}
		public static string EncodeNumber(double value)
		{
			return value.ToString(CultureInfo.InvariantCulture);
		}
		public static double ParseNumber(string jsonNumber)
		{
			return double.Parse(jsonNumber, CultureInfo.InvariantCulture);
		}

		private static HashSet<Type> m_NumberTypes = new HashSet<Type>(new Type[] {
			typeof(byte),
			typeof(short),
			typeof(int),
			typeof(long),
			typeof(ushort),
			typeof(uint),
			typeof(ulong),
			typeof(double),
			typeof(float),
			typeof(decimal)
		});
		public static JsonValue EncodeObject(object obj)
		{
			if (obj is JsonValue)
				return obj as JsonValue;
			if (obj == null)
				return null;
			if (obj is Boolean)
				return new JsonBoolean((bool)obj);
			if ((obj is string) || (obj is char))
				return new JsonString(obj.ToString());
			if (m_NumberTypes.Contains(obj.GetType()))
				return new JsonNumber(double.Parse(obj.ToString()));
			if (obj is System.Collections.IDictionary)
			{
				JsonObject o = new JsonObject();
				foreach (System.Collections.DictionaryEntry entry in (System.Collections.IDictionary)obj)
					o.Add(entry.Key.ToString(), EncodeObject(entry.Value));
				return o;
			}
			if(obj is System.Collections.IEnumerable)
			{
				JsonArray array = new JsonArray();
				foreach (object o in (System.Collections.IEnumerable)obj)
					array.Add(EncodeObject(o));
				return array;
			}
			if (obj is DateTime)
			{
				TimeSpan t = (((DateTime)obj) - new DateTime(1970, 1, 1));
				// unix time since epoch (can be created in Js with a new Date(totalSecond);)
				return new JsonNumber((int)t.TotalSeconds);
			}
			return new JsonString(obj.ToString());
		}
		public static JsonObject EncodeObjectByReflection(object obj, bool include, params string[] propertyNames)
		{
			HashSet<string> names = new HashSet<string>(propertyNames);
			Type type = obj.GetType();
			PropertyInfo[] properties = type.GetProperties();
			JsonObject o = new JsonObject();
			foreach (PropertyInfo p in properties)
			{
				if (names.Contains(p.Name) == include)
				{
					object value = p.GetValue(obj, null);
					o.Add(p.Name, EncodeObject(value));
				}
			}
			return o;
		}
		public static JsonValue ParseJson(string json)
		{
			int index = 0;
			return ParseJson(json, ref index);
		}
		public static JsonValue ParseJson(string json, ref int index)
		{
			while (index < json.Length && (char.IsWhiteSpace(json[index]) || char.IsControl(json[index])))
				index++;
			char cur = json[index];
			int startIndex = index;
			index ++;
			if (cur == '{')
			{
				JsonObject obj = new JsonObject();
				while (json[index] != '}')
				{
					JsonString key = ParseJson(json, ref index) as JsonString;
					while (index < json.Length && (char.IsWhiteSpace(json[index]) || char.IsControl(json[index])))
						index++;
					if (json[index] != ':')
						throw new Exception();
					index++;
					JsonValue value = ParseJson(json, ref index);
					obj.Add(key.Value, value);
					while (index < json.Length && (char.IsWhiteSpace(json[index]) || char.IsControl(json[index])))
						index++;
					if (json[index] != ',' && json[index] != '}')
						throw new Exception();
					if (json[index] == ',')
					{
						index++;
						while (index < json.Length && (char.IsWhiteSpace(json[index]) || char.IsControl(json[index])))
							index++;
					}
				}
				index++;
				return obj;
			}
			else if (cur == '[')
			{
				JsonArray arr = new JsonArray();
				while (json[index] != ']')
				{
					JsonValue value = ParseJson(json, ref index);
					arr.Add(value);
					while (index < json.Length && (char.IsWhiteSpace(json[index]) || char.IsControl(json[index])))
						index++;
					// syntax error
					while (index < json.Length && json[index] != ',' && json[index] != ']')
						index++;
					//if (json[index] != ',' && json[index] != ']')
					//   throw new Exception();
					if (json[index] == ',')
					{
						index++;
						while (index < json.Length && (char.IsWhiteSpace(json[index]) || char.IsControl(json[index])))
							index++;
					}
				}
				index++;
				return arr;
			}
			else if (cur == '"')
			{
				while (json[index] != '"')
				{

					if (json[index] == '\\')
					{
						index++;
						if (json[index] == 'u')
							index += 4;
					}
					index++;
				}
				index++;
				return new JsonString(ParseString(json.Substring(startIndex, index - startIndex)));
			}
			else if (cur == Null[0])
			{
				index += Null.Length -1;
				return null;
			}
			else if (cur == True[0])
			{
				index += True.Length - 1;
				return new JsonBoolean(true);
			}
			else if (cur == False[0])
			{
				index += False.Length - 1;
				return new JsonBoolean(false);
			}
			else if (char.IsLetter(cur))
			{
				// some reponse do note quotes their object's keys (ie google)
				while (json[index] != ' ' && json[index] != ':' && json[index] != ',')
					index++;
				return new JsonString(ParseString('"' + json.Substring(startIndex, index - startIndex) + '"'));
			}
			else
			{
				if (cur != '-' && !char.IsDigit(cur))
					throw new Exception();
				if (cur != '-')
					index--;
				if (json[index] == '0')
					index++;
				else if (json[index] >= '0' && json[index] <= '9')
				{
					while (index < json.Length && char.IsDigit(json[index]))
						index++;
				}
				else
					throw new Exception();
				if (json.Length < index && json[index] == '.')
				{
					index++;
					while (index < json.Length && char.IsDigit(json[index]))
						index++;
				}
				if (index < json.Length && char.ToLower(json[index]) == 'e')
				{
					index++;
					if (json[index] == '+' || json[index] == '-')
						index++;
					while (index < json.Length && char.IsDigit(json[index]))
						index++;
				}
				return new JsonNumber(ParseNumber(json.Substring(startIndex, index - startIndex)));
			}
		}
	}
}
